package com.gitee.lichoking.mysoap.util;

import com.gitee.lichoking.mysoap.annotation.XNameSpace;
import com.gitee.lichoking.mysoap.annotation.XNode;
import com.gitee.lichoking.mysoap.common.SoapException;
import com.gitee.lichoking.mysoap.convert.Converter;
import com.gitee.lichoking.mysoap.convert.DefaultConverterLoader;
import com.gitee.lichoking.mysoap.reflection.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.dom4j.xpath.DefaultXPath;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * soap工具
 *
 * @author lich
 * @date 2020/08/04
 */
@Slf4j
public class SoapUtils {
    /**
     * 请求
     *
     * @param url
     * @param action
     * @param requestXml
     * @return
     * @throws IOException
     */
    public static String request2String(String url, String action, String requestXml) throws IOException {
        InputStream is = request2InputStream(url, action, requestXml);
        byte[] bts = readInputStream(is);
        is.read(bts);
        return new String(bts, "UTF-8");
    }

    /**
     * 请求
     *
     * @param url
     * @param action
     * @param requestXml
     * @return
     * @throws IOException
     */
    public static InputStream request2InputStream(String url, String action, String requestXml) throws IOException {
        log.debug("url:{}", url);
        log.debug("action:{}", action);
        log.debug("requestXml:{}", requestXml);
        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
        connection.setRequestProperty("SOAPAction", action);
        connection.setRequestProperty("Transfer-Encoding", "chunked");
        connection.connect();//连接服务器
        OutputStream os = null;
        try {
            os = connection.getOutputStream();
            os.write(requestXml.getBytes("UTF-8"));
        } catch (IOException e) {
            throw e;
        } finally {
            if (os != null) {
                os.flush();
                os.close();
            }
        }
        //获取数据，真正的请求从此处开始
        return connection.getInputStream();
    }

    private static byte[] readInputStream(InputStream inputStream) throws IOException {
        byte[] buffer = new byte[1024];
        int len = 0;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while ((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        bos.close();
        return bos.toByteArray();
    }

    public static <T> T request2Object(String url, String action, String requestXml, Class<T> returnType) throws IOException {
        String xml = request2String(url, action, requestXml);
        Object object = getObject(returnType, xml);
        return (T) object;
    }

    private static Object getObject(Class<?> type, String xml) {
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(new ByteArrayInputStream(xml.getBytes(Charset.defaultCharset())));
        } catch (DocumentException e) {
            log.error("错误xml返回：{}", xml);
            throw new SoapException("返回xml错误");
        }

        Object object = null;
        try {
            object = type.newInstance();
            Map<String, String> namespaceURIs = getNamespaceURIs(type);

            Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                XNode xNode = field.getAnnotation(XNode.class);
                //跳过没XNode注解的field
                if (xNode == null) {
                    continue;
                }
                XNode.GetMode getMode = xNode.getMode();
                DefaultXPath xpath = new DefaultXPath(xNode.value());
                if (!namespaceURIs.isEmpty()) {
                    xpath.setNamespaceURIs(namespaceURIs);
                }
                Class<?> fieldType = field.getType();
                if (fieldType.isArray()) {
                    throw new SoapException("不支持Array，请使用List");
                }
                if (List.class.isAssignableFrom(fieldType)) {
                    if (XNode.GetMode.XML.equals(getMode)) {
                        throw new SoapException("List参数不支持GetMode.XML");
                    }

                    List<Node> nodes = xpath.selectNodes(document);
                    if (nodes == null || nodes.isEmpty()) {
                        throw new SoapException(xpath + "不存在");
                    }
                    Type genericType = field.getGenericType();
                    if (null == genericType) {
                        continue;
                    }
                    if (genericType instanceof ParameterizedType) {
                        ParameterizedType pt = (ParameterizedType) genericType;
                        // 得到泛型里的class类型对象
                        Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
                        Converter<?> converter = DefaultConverterLoader.loadConverter(actualTypeArgument);
                        List<Object> fieldObjects = new ArrayList<>();
                        if (converter == null) {
                            for (Node node : nodes) {
                                String subXml = node.asXML();
                                Object fieldObject = getObject(actualTypeArgument, subXml);
                                fieldObjects.add(fieldObject);
                            }
                        } else {
                            for (Node node : nodes) {
                                Object fieldObject = converter.nodeTextToJavaData(node);
                                fieldObjects.add(fieldObject);
                            }
                        }
                        ReflectionUtils.set(object, field, fieldObjects);
                    }
                } else {
                    Node node = xpath.selectSingleNode(document);
                    if (node == null) {
                        throw new SoapException(xpath + "不存在");
                    }
                    if (XNode.GetMode.XML.equals(getMode)) {
                        String nodeXml = node.asXML();
                        ReflectionUtils.set(object, field, nodeXml);
                    } else {
                        Converter<?> converter = DefaultConverterLoader.loadConverter(field.getType());
                        if (converter == null) {
                            //递归生成非基本类型
                            String subXml = node.asXML();
                            Object fieldObject = getObject(fieldType, subXml);
                            ReflectionUtils.set(object, field, fieldObject);
                        } else {
                            Object fieldObject = converter.nodeTextToJavaData(node);
                            ReflectionUtils.set(object, field, fieldObject);
                        }
                    }
                }
            }
        } catch (InstantiationException e) {
            throw new SoapException(type.getName() + "实例化失败");
        } catch (IllegalAccessException e) {
            throw new SoapException("非法访问");
        }
        return object;
    }

    private static Map<String, String> getNamespaceURIs(Class<?> type) {
        Map<String, String> namespaceURIs = new HashMap<>(16);
        XNameSpace xNameSpace = type.getAnnotation(XNameSpace.class);
        if (xNameSpace != null) {
            String[] value = xNameSpace.value();
            for (int i = 0; i < value.length; i++) {
                if (StringUtils.isEmpty(value) || !value[i].contains(":")) {
                    throw new SoapException("@XNameSpace注解value格式错误");
                }
                int index = value[i].indexOf("=");
                if (index == -1) {
                    throw new SoapException("@XNameSpace分隔符为=，示例：xsi=http://www.w3.org/2001/XMLSchema-instance");
                }
                namespaceURIs.put(value[i].substring(0, index), value[i].substring(index + 1));
            }
        }
        return namespaceURIs;
    }
}